#!/usr/bin/perl

use strict;
use Data::Dumper;
use Getopt::Std;
use IO::File;
our %opts = (d => 'size-tests');

# default arguments go here
my @sets =
    ({
	"with-defaults" => '',
     });

my %base_args = %{$sets[0]};
my ($basename, $basenum);

getopts('t:hlcD:nj:o:eb:T', \%opts) || usage();

$opts{'j'} = "-j$opts{j}" if (exists($opts{'j'}));

usage() if ($opts{'h'});

my @argumentsets =
    (
     {
	 title => 'security-types',
	 arguments =>
	 {
	     usm => {
		 'with-security-modules' => 'usm',
	     },
	     dtlsandtls => {
		 'with-security-modules'     => 'tsm',
		 'with-out-security-modules' => 'usm',
		 'with-transports'           => 'DTLSUDP TLSTCP',
		 'with-mib-modules'          => 'tlstm-mib tsm-mib'
	     },
	     usmanddtlsandtls => {
		 'with-security-modules'     => 'tsm usm',
		 'with-transports'           => 'DTLSUDP TLSTCP',
		 'with-mib-modules'          => 'tlstm-mib tsm-mib'
	     }
	 }
     },
     {
	 title => 'minimalist',
	 arguments =>
	 { 
	     minimal => {
		 'enable-minimalist'    => '',
	     },
	     'not-minimal' => {
	     }
	 }
     },
     {
	 title => 'mib-loading',
	 arguments =>
	 { 
	     'without-mib-loading' => {
		 'disable-mib-loading'    => '',
	     },
	     'with-mib-loading' => {
	     }
	 }
     },
     {
	 title => 'debugging',
	 arguments =>
	 { 
	     'without-debugging' => {
		 'disable-debugging'    => '',
	     },
	     'with-debugging' => {
	     }
	 }
     },
     {
	 title => 'logging',
	 arguments =>
	 { 
	     'without-logging' => {
		 'with-out-features'    => 'logging',
	     },
	     'with-logging' => {
	     }
	 }
     },
     {
	 title => 'agent-mibs',
	 arguments =>
	 { 
	     'full-agent' => {
	     },
	     'mini-agent' => {
		 'enable-mini-agent' => '',
	     }
	 }
     },
     {
	 title => 'protocol-support',
	 arguments =>
	 {
	     'everything' => {
	     },
	     'read-only' => {
		 'enable-read-only' => '',
	     },
	     'notify-only' => {
		 'enable-notify-only' => '',
	     }
	 }
     },
     {
	 title => 'perl',
	 arguments =>
	 { 
	     'without-perl-modules' => {
		 'without-perl-modules'    => '',
		 'disable-embedded-perl' => '',
	     },
	     'with-perl-no-embedding' => {
		 'with-perl-modules'    => '',
		 'disable-embedded-perl' => '',
	     },
	     'with-perl-and-embedding' => {
		 'with-perl-modules'    => '',
		 'enable-embedded-perl' => '',
	     }
	 }
     },
    );

# map these to a referencable lookup hash
my %argumentsets;
foreach my $set (@argumentsets) {
    $argumentsets{$set->{'title'}} = $set;
}

if ($opts{'l'}) {
    print "Types available:\n";
    printf("  %-40.40s %s\n", "Test Title", "Number of subtests");
    printf("  %-40.40s %s\n", "-" x 39, "-" x length("Number of subtests"));
    foreach my $type (@argumentsets) {
	my @keys = keys(%{$type->{'arguments'}});
	printf("  %-40.40s %s\n", $type->{'title'}, $#keys+1);
    }
    exit;
}

my %types;
if ($opts{'t'}) {
    my @types = split(/,\s*/, $opts{'t'});
    foreach my $type (@types) {
	$types{$type} = 1;
    }
}

if ($opts{'b'}) {
    # use this set as the base default set of arguments
    ($basename, $basenum) = ($opts{'b'} =~ /(.*):(\d+)/);
    if (!exists($argumentsets{$basename})) {
	printf STDERR "failed to find set for -b argument: %s\n", $basename;
	exit(1);
    }
    @sets = add_in_flags($argumentsets{$basename}, \%base_args, ());
    @sets = @sets[$basenum];
    %base_args = %{$sets[0]};
}

foreach my $set (@argumentsets) {
    if (!$opts{'t'} || exists($types{$set->{'title'}})) {
        if ($basename && $set->{'title'} eq $basename) {
            next;
        }
	@sets = add_in_flags($set, \%base_args, @sets);
    }
}

if ($opts{'c'}) {
    # print the configure flags
    foreach my $set (@sets) {
	print "$set->{'title'}:\n";
	print " ", generate_configure_flags($set), "\n";
    }
    exit;
}

my $summaryfile;
if ($opts{'o'} && !$opts{'n'}) {
    $summaryfile = new IO::File;
    $summaryfile->open(">$opts{o}");
}

my $count = 0;
foreach my $set (@sets) {
    $count++;
    build_set($count, $set);
}

sub add_in_flags {
    my ($argumentset, $base_flags, @current_flags) = @_;

    my @new_flags;

    # do a linear search of the options
    if (! $opts{'e'}) {
	@new_flags = @current_flags;
	foreach my $newargs (keys(%{$argumentset->{'arguments'}})) {
	    my %flags = %$base_flags;

	    $flags{'title'} = "$flags{'title'}:$newargs";

	    foreach my $newflag (keys(%{$argumentset->{'arguments'}{$newargs}})) {

		$flags{$newflag} .= " $argumentset->{'arguments'}{$newargs}{$newflag}";
	    }
	    push @new_flags, \%flags;
	}
	return @new_flags;
    }

    # or an exponential search
    foreach my $flags (@current_flags) {
	foreach my $newargs (keys(%{$argumentset->{'arguments'}})) {
	    my %flags = %{$flags};  # copy the existing hash-set of flags
	    
	    if (exists($flags{'title'})) {
		$flags{'title'} .= ", $newargs";
	    } else {
		$flags{'title'} .= "$newargs";
	    }
	    foreach my $newflag (keys(%{$argumentset->{'arguments'}{$newargs}})) {

		$flags{$newflag} .= " $argumentset->{'arguments'}{$newargs}{$newflag}";
	    }

	    push @new_flags, \%flags;
	}
    }

    return @new_flags;
}

sub generate_configure_flags {
    my ($arguments) = @_;
    my $line = "";
    foreach my $arg (keys(%$arguments)) {
	next if ($arg eq 'title');
	if ($arguments->{$arg} =~ /^\s*$/) {
	    $line .= " --$arg";
	} else {
	    $line .= " --$arg=\"$arguments->{$arg}\"";
	}
    }
    return $line;
}

sub build_set {
    my ($num, $arguments) = @_;

    $num = sprintf("%03d", $num);

    my $file;

    if (!$opts{'n'}) {
	mkdir $opts{'d'} if (! -d $opts{'d'});
	die "failed to create the $opts{'d'} directory" if (! -d $opts{'d'});

	$file = new IO::File;
	$file->open(">$opts{'d'}/$num-0-cmd-summary");
	$file->print("Creating output for: $arguments->{'title'}\n");
    }

    print "==================== $arguments->{'title'}\n";


    System ($file, $num, "1-distclean", "make distclean");
    System ($file, $num, "2-configure",
	    "./configure " . generate_configure_flags($arguments));
    System ($file, $num, "3-features", "make features");
    System ($file, $num, "4-make", "make $opts{'j'}");
    System ($file, $num, "5-unused-code", "perl local/minimalist/find-unused-code -g");
    System ($file, $num, "6-testing", "make -C testing test") if ($opts{'T'});

    if (!$opts{'n'}) {
	analyze_size($arguments->{'title'}, "$opts{'d'}/$num-9-results");
    }
}

sub analyze_size {
    my ($title, $filename) = @_;

    my $outfile = new IO::File;
    $outfile->open(">$filename");

    print "Results for: $title\n";
    printf $outfile "Results for: $title\n";
    printf ("%-50.50s %10s\n", "-" x 49, "-" x 10);
    printf $outfile ("%-50.50s %10s\n", "-" x 49, "-" x 10);

    my $totalsize = 0;
    foreach my $buildfile (glob("snmplib/.libs/lib*.so.*.0.0"),
			   glob("agent/.libs/lib*.so.*.0.0"),
			   "agent/.libs/snmpd") {

	my @info = stat($buildfile);
	printf $outfile ("%-50.50s %10s\n", $buildfile, $info[7]);
	printf("%-50.50s %10s\n", $buildfile, $info[7]);
	$totalsize += $info[7];
    }
    printf $outfile ("%-50.50s %10s\n", "-" x 49, "-" x 10);
    printf $outfile ("%-50.50s %10s bytes\n", "TOTAL", $totalsize);

    printf("%-50.50s %10s\n", "-" x 49, "-" x 10);
    printf("%-50.50s %10s bytes\n", "TOTAL", $totalsize);

    if (defined($summaryfile)) {
	my $restricted_title = $title;
	$restricted_title =~ s/[^a-zA-Z]/_/g;
	printf $summaryfile "%10s %s \n", $totalsize, $title;
    }

    return $totalsize;
}

sub usage {
    print "Usage: $0 [FLAGS]\n\n";
    print "FLAGS:\n";
    print "  -h\t\thelp\n";
    print "  -t TYPES\tSelect types to analyze (default = all)\n";
    print "  -l\t\tList available types\n";
    print "  -c\t\tPrint the configuration flags that would be used\n";
    print "  -D DIR\tSave data results to this directory\n";
    print "  -o FILE\tSave a complete summary chart to FILE\n"; 
    print "  -n\t\tDry run -- only show the commands to execute\n";
    print "  -j NUM\tExecute make with parallel building (-jNUM)\n";
    print "  -e\t\tUse exponential expansion (all combos) of the requested options\n";
    print "  -b NAME:NUM\tUse NAME and the NUM'th set as the base arguments to start from\n";
    print "  -T\t\tRun 'make test' at each step as well\n";
    exit;
}

sub System {
    my $file  = shift;
    my $num   = shift;
    my $title = shift;
    my $pipe = " 2>&1 | tee $opts{'d'}/$num-$title.out\n";

    print "### ", join(" ", @_), $pipe;
    if (!$opts{'n'} && $file) {
	print $file "### ", join(" ", @_), "\n";
    }

    if (!$opts{'n'}) {
	system(join(" ", @_) . $pipe);
    }
}
